﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using MTGenome.MTG;

namespace MTGenome.Gatherer
{
    internal static class HtmlParser
    {
        private static readonly Regex _nameQueryString = new Regex("&amp;name=([^&]+)?&amp;", RegexOptions.Compiled),
            _setQueryString = new Regex("&amp;set=([^&]+)?&amp;", RegexOptions.Compiled),
            _multiverseQueryString = new Regex("Details.aspx\\?multiverseid=(\\d+)\"", RegexOptions.Compiled);
        private const string STD_XPATH = "//div[@id='ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_{0}']/div[@class='value']";

        public static IEnumerable<int> ParseMultiverseIDs(string html)
        {
            foreach (Match match in _multiverseQueryString.Matches(html))
            {
                int x = 0;
                if (int.TryParse(match.Groups[1].Value, out x))
                {
                    yield return x;
                }
            }
        }

        public static Card Parse(int multiverseID, string html)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);

            string manaCost = doc.DocumentNode
                .SelectNodesSafe(string.Format(STD_XPATH + "/img", "manaRow"))
                .DecodeManaCost();
            Color color = Color.Colorless;
            Expansion exp = new Expansion();
            int? power = null,
                toughness = null,
                loyalty = null;

            if (manaCost.Contains("U"))
            {
                color |= Color.Blue;
            }
            if (manaCost.Contains("B"))
            {
                color |= Color.Black;
            }
            if (manaCost.Contains("R"))
            {
                color |= Color.Red;
            }
            if (manaCost.Contains("G"))
            {
                color |= Color.Green;
            }
            if (manaCost.Contains("W"))
            {
                color |= Color.White;
            }
            exp.ID = doc.DocumentNode
                .SelectNodesSafe(string.Format(STD_XPATH + "/div/a/img[@src]", "setRow"))
                .Select(h => DecodeSet(h.GetAttributeValue("src", string.Empty)))
                .FirstOrDefault();
            exp.Name = doc.DocumentNode
                .SelectNodesSafe(string.Format(STD_XPATH + "/div/a", "setRow"))
                .Select(h => DecodeString(h.InnerHtml))
                .Skip(1)
                .FirstOrDefault();
            string powerAndToughness = doc.DocumentNode
                .SelectNodesSafe(string.Format(STD_XPATH, "ptRow"))
                .Select(h => DecodeString(h.InnerHtml))
                .FirstOrDefault();

            if (!string.IsNullOrEmpty(powerAndToughness))
            {
                if (powerAndToughness.Contains('/'))
                {
                    string[] split = powerAndToughness.Split('/');
                    power = DecodeInt(split[0]);
                    toughness = DecodeInt(split[1]);
                }
                else
                {
                    loyalty = DecodeInt(powerAndToughness);
                }
            }

            return new Card
                {
                    MultiverseID = multiverseID,
                    ManaCost = manaCost,
                    Color = color,
                    Expansion = exp.ID,
                    ExpansionName = exp.Name,
                    OtherMultiverseIDs = ParseMultiverseIDs(html)
                        .Where(i => i != multiverseID)
                        .Distinct()
                        .ToArray(),
                    Name = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH, "nameRow"))
                        .Select(h => DecodeString(h.InnerHtml))
                        .FirstOrDefault(),
                    ConvertedManaCost = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH, "cmcRow"))
                        .Select(h => DecodeByte(h.InnerHtml))
                        .FirstOrDefault(),
                    Type = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH, "typeRow"))
                        .Select(h => DecodeString(h.InnerHtml))
                        .FirstOrDefault(),
                    Text = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH + "/div", "textRow"))
                        .Select(n => n.ChildNodes)
                        .DecodeCardText(),
                    FlavorText = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH + "/div/i", "flavorRow"))
                        .Select(h => DecodeString(h.InnerHtml))
                        .FirstOrDefault(),
                    Rarity = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH + "/span", "rarityRow"))
                        .Select(h => DecodeRarity(h.InnerHtml))
                        .FirstOrDefault(),
                    Artist = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH + "/a", "artistRow"))
                        .Select(h => DecodeString(h.InnerHtml))
                        .FirstOrDefault(),
                    CardNumber = doc.DocumentNode
                        .SelectNodesSafe(string.Format(STD_XPATH, "numberRow"))
                        .Select(h => DecodeInt(h.InnerHtml))
                        .FirstOrDefault(),
                    Power = power,
                    Toughness = toughness,
                    Loyalty = loyalty,
                    PowerToughnessText = powerAndToughness,
                };
        }

        private static IEnumerable<HtmlNode> SelectNodesSafe(this HtmlNode node, string xpath)
        {
            return node.SelectNodes(xpath) ?? Enumerable.Empty<HtmlNode>();
        }

        private static string DecodeString(string value)
        {
            return WebUtility.HtmlDecode(value).Replace("<br>", string.Empty).Trim();
        }

        private static string DecodeSet(string value)
        {
            return _setQueryString.Match(value).Groups[1].Value;
        }

        private static Rarity DecodeRarity(string value)
        {
            switch (value.ToLower())
            {
                case "mythic rare":
                    return Rarity.MythicRare;
                case "rare":
                    return Rarity.Rare;
                case "uncommon":
                    return Rarity.Uncommon;
                case "common":
                default:
                    return Rarity.Common;
            }
        }

        private static string DecodeCardText(this IEnumerable<HtmlNodeCollection> nodes)
        {
            if (nodes == null)
            {
                return string.Empty;
            }
            StringBuilder builder = new StringBuilder();
            foreach (HtmlNodeCollection childNodes in nodes)
            {
                foreach (HtmlNode node in childNodes)
                {
                    if (node.NodeType == HtmlNodeType.Text)
                    {
                        builder.Append(node.InnerText);
                    }
                    else if (node.NodeType == HtmlNodeType.Element)
                    {
                        if (node.Name == "img")
                        {
                            string name = _nameQueryString.Match(node.GetAttributeValue("src", string.Empty)).Groups[1].Value;
                            if (!string.IsNullOrEmpty(name))
                            {
                                builder.Append('[');
                                builder.Append(name);
                                builder.Append(']');
                            }
                        }
                    }
                }

                builder.AppendLine();
            }
            return builder.ToString().Trim();
        }

        private static string DecodeManaCost(this IEnumerable<HtmlNode> nodes)
        {
            //Might switch to StringBuilder
            string text = string.Empty;
            foreach (var node in nodes)
            {
                text += _nameQueryString.Match(node.GetAttributeValue("src", string.Empty)).Groups[1].Value;
            }
            return text;
        }

        private static byte DecodeByte(string value)
        {
            byte x = 0;
            byte.TryParse(DecodeString(value), out x);
            return x;
        }

        private static int DecodeInt(string value)
        {
            int x = 0;
            int.TryParse(DecodeString(value), out x);
            return x;
        }
    }
}
